#include "raytracer.h"

RayTracer::RayTracer(SceneParser *s, int max_bounces, float cutoff_weight, bool shadows, bool ispathtracing)
{
	scene = s;
	this->max_bounces = max_bounces;
	this->cutoff_weight = cutoff_weight;
	this->shadows = shadows;
	this->ispathtracing = ispathtracing;
}

d_Vec3f RayTracer::mirrorDirection(const d_Vec3f &normal, const d_Vec3f &incoming) const
{
	return incoming-normal*2*dot(incoming, normal);
}

d_Vec3f randOverHemisphere(d_Vec3f normal)
{
	d_Vec3f result = norm(d_Vec3f(rand()/(float)RAND_MAX - 0.5, rand()/(float)RAND_MAX - 0.5, rand()/(float)RAND_MAX - 0.5));
	if(dot(result,normal)<0)
		result = result*(-1);
	return result;
}

d_Vec3f RayTracer::traceRay(Ray &ray, float tmin, int bounces, float weight, Hit &hit) const
{
	Group *group = scene->getGroup();
	
	if(group->intersect(ray, hit, tmin))
	{
		if(scene->getNumLights()==0)
			return hit.getMaterial()->getDiffuseColor(ray.pointAtParameter(hit.getT()));
		else
		{
			d_Vec3f sum=scene->getAmbientLight()*hit.getMaterial()->getDiffuseColor(ray.pointAtParameter(hit.getT()));
			
			for(int k=0; k<scene->getNumLights(); k++)
			{
				Light *light = scene->getLight(k);
				d_Vec3f dir, col;
				float dist;
				light->getIllumination(ray.pointAtParameter(hit.getT()), dir, col, dist);
				
				//if(len(dir)<EPSILON)
				//	dir = randOverHemisphere(hit.getNormal());
				if(shadows)
				{
					Hit shadowhit(dist, NULL, d_Vec3f(0,0,0));
					if(!group->intersect(Ray(ray.pointAtParameter(hit.getT()), dir), shadowhit, EPSILON)) 
						sum+= hit.getMaterial()->Shade(ray, hit, dir, col);
						//sum+= shadowhit.getMaterial()->getTransparentColor(d_Vec3f(0,0,0));
				}
				else
					sum += hit.getMaterial()->Shade(ray, hit, dir, col);
			}
			//return sum;
			if(ispathtracing)
			{
				if(bounces>0)
				{
					d_Vec3f randvec = randOverHemisphere(hit.getNormal());
					Ray randomray(ray.pointAtParameter(hit.getT()), randvec);
					Hit tracehit(10000, NULL, d_Vec3f(0,0,0));
					float cost = dot(randvec,hit.getNormal());
					
					float newweight = weight*len(hit.getMaterial()->getReflectiveColor(ray.pointAtParameter(hit.getT())));
					d_Vec3f color = traceRay(randomray, EPSILON, --bounces, newweight, tracehit);
					//cout << color << endl;
					d_Vec3f BRDF = hit.getMaterial()->Shade(ray,hit,randvec,color);
					//cout << BRDF << endl;
					sum+= BRDF;//*cost;
				}
			}

			if(len(hit.getMaterial()->getReflectiveColor(ray.pointAtParameter(hit.getT())))>EPSILON)
			{
				if(bounces > 0 && weight < 1)
				{
					Ray mirrorray(ray.pointAtParameter(hit.getT()), mirrorDirection(hit.getNormal(),ray.getDirection()));
					Hit mirrorhit(10000, NULL, d_Vec3f(0,0,0));
					sum+= hit.getMaterial()->getReflectiveColor(ray.pointAtParameter(hit.getT())) * traceRay(mirrorray, EPSILON, --bounces, weight*len(hit.getMaterial()->getReflectiveColor(ray.pointAtParameter(hit.getT()))) ,mirrorhit);
				}
			}
			if(len(hit.getMaterial()->getTransparentColor(ray.pointAtParameter(hit.getT())))>EPSILON)
			{
				
				if(bounces > 0 && weight < 1)
				{
					d_Vec3f refDirection;
					if(dot(hit.getNormal(),ray.getDirection())<0)
					{

						refDirection = transmittedDirection(hit.getNormal(),ray.getDirection(),hit.getMaterial()->getRefractionIndex(),1);
						//cout << "este" << endl;
						
					}
					else 
					{
						refDirection = transmittedDirection(hit.getNormal(),ray.getDirection(),1,hit.getMaterial()->getRefractionIndex());
						//cout << "talvez" << endl;
					}
					//Ray mirrorray(ray.pointAtParameter(hit.getT()), mirrorDirection(hit.getNormal(),ray.getDirection()));
					Hit refhit(10000, NULL, d_Vec3f(0,0,0));
					Ray refray(ray.pointAtParameter(hit.getT()), refDirection);
					//cout << refray << endl;
					sum+=hit.getMaterial()->getTransparentColor(ray.pointAtParameter(hit.getT()))*traceRay(refray, EPSILON, --bounces, weight*len(hit.getMaterial()->getTransparentColor(ray.pointAtParameter(hit.getT()))) ,refhit);
				}
			}
			return sum;
		}
	}
	else
	{
		//if(ispathtracing)
		return scene->getBackgroundColor();
	}
}

d_Vec3f RayTracer::pathTraceRay(Ray &ray, float tmin, int bounces, float weight, Hit &hit) const
{
	Group *group = scene->getGroup();
	
	if(group->intersect(ray, hit, tmin))
	{
		d_Vec3f point = ray.pointAtParameter(hit.getT());
		d_Vec3f normal = hit.getNormal();
		return shade(ray, hit, bounces);
	}
				
} 

d_Vec3f RayTracer::shade(Ray &ray, Hit &hit, int bounces) const
{
	Group *group = scene->getGroup();
	d_Vec3f color = d_Vec3f(0,0,0);
	for(int i=0; i<scene->getNumLights(); i++)
	{
		Light *light = scene->getLight(i);
		d_Vec3f dir, col;
		float dist;
		light->getIllumination(ray.pointAtParameter(hit.getT()), dir, col, dist);
		
		Hit shadowhit(dist, NULL, d_Vec3f(0,0,0));
		if(!group->intersect(Ray(ray.pointAtParameter(hit.getT()), dir), shadowhit, EPSILON)) 
		{
			color += hit.getMaterial()->Shade(ray, hit, dir, col);
		}
	}
	
	d_Vec3f randvec = randOverHemisphere(hit.getNormal());
	Ray randomray(ray.pointAtParameter(hit.getT()), randvec);
	Hit tracehit(10000, NULL, d_Vec3f(0,0,0));
	color += pathTraceRay(randomray, EPSILON, bounces, 1,tracehit);
}

d_Vec3f RayTracer::transmittedDirection(d_Vec3f normal, d_Vec3f incoming, float index_i, float index_t) const
{
	float nr = index_t/index_i;
	float disc = 1-pow(nr,2)*(1-pow(dot(normal,incoming),2));
	//if(disc <0)
	//	disc = 0;
	float c1 = -dot( normal, incoming );
	float c2 = sqrt( 1 - nr*nr * (1 - c1*c1) );
	//Rr = (n * V) + (n * c1 - c2) * N
	return norm(incoming*nr + normal*(-nr*c1-c2));
	//return norm((nr*dot(normal,incoming)-sqrt(disc))*normal-nr*(incoming));
	//return vl_zero;
	//return -incoming-2*dot(-incoming, normal)*normal;
}
